import os
import sys
import json
import math
import shutil
import datetime
import time
import threading
import base64
import csv
import sqlite3
import socket
import webbrowser
from io import BytesIO
from PIL import Image
from flask import Flask, render_template, request, jsonify, send_from_directory, redirect, url_for
from flask_socketio import SocketIO
import logging
import pystray

# 导入PyInstaller打包时需要的threading驱动（解决"Invalid async_mode specified"错误）
# 使用别名避免与Python内置threading模块冲突
from engineio.async_drivers import threading as engineio_threading

# 导入下单管理模块
import xiadan

# pyinstaller --onefile --noconsole --name 任务统计_v18 任务统计.py

# 获取基础目录
base_dir = ""
if getattr(sys, 'frozen', False):
    # 如果是打包后的exe
    base_dir = os.path.dirname(sys.executable)
else:
    # 如果是脚本运行
    base_dir = os.path.dirname(os.path.abspath(__file__))

# 端口号和地址设置
端口号 = 11435
端口号 = 80
内网地址 = f"http://127.0.0.1:{端口号}"
外网地址 = 内网地址

# 操作日志文件名和表头
操作日志文件名 = "操作日志.csv"
操作日志表头 = ["姓名", "时间", "操作对象", "操作类型"]

# 读取外网地址
try:
    # with open(os.path.join(base_dir, "外网地址.txt"), "r") as f:
    #     外网地址 = f.readline().strip()
    pass
except:
    print("未找到外网地址文件，使用内网地址")



# 创建系统托盘图标
def create_tray_icon():
    # 创建一个简单的图标（16x16像素的蓝色方块）
    icon_image = Image.new('RGB', (16, 16), color='blue')
    
    def on_clicked(icon, item):
        if str(item) == "打开网页":
            webbrowser.open(f'http://{get_local_ip()}:5000')
        elif str(item) == "退出":
            icon.stop()
            os._exit(0)
    
    menu = pystray.Menu(
        pystray.MenuItem("打开网页", on_clicked),
        pystray.MenuItem("退出", on_clicked)
    )
    
    icon = pystray.Icon("任务统计系统", icon_image, "任务统计系统", menu)
    return icon

# 启动托盘图标（在后台线程中运行）
def start_tray_icon():
    try:
        icon = create_tray_icon()
        icon.run()
    except Exception as e:
        logging.error(f"托盘图标启动失败: {e}")

# 配置日志
def setup_logging():
    log_dir = "logs"
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)
    
    log_file = os.path.join(log_dir, f"任务统计_{datetime.datetime.now().strftime('%Y%m%d')}.log")
    
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(log_file, encoding='utf-8'),
            logging.StreamHandler()  # 同时输出到控制台（如果有的话）
        ]
    )
    logging.info("程序启动")

# 在程序开始时调用
setup_logging()

# 启动托盘图标
tray_thread = threading.Thread(target=start_tray_icon, daemon=True)
tray_thread.start()

# 获取局域网IP地址
def get_local_ip():
    """获取本机在局域网中的IP地址"""
    try:
        # 创建一个socket连接，用来获取本机的局域网IP
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 连接任意可达的外部地址，不会真正建立连接
        s.connect(("8.8.8.8", 80))
        # 获取本机的局域网IP
        ip = s.getsockname()[0]
        s.close()
        return ip
    except:
        return "127.0.0.1"  # 如果获取失败，返回本地回环地址

# 自动打开浏览器
def open_browser_pages(local_ip, port):
    """自动打开xiadan页面"""
    def open_pages():
        # 等待服务器启动
        time.sleep(2)
        
        # 构建URL
        xiadan_url = f"http://{local_ip}:{port}/xiadan"
        
        try:
            print(f"正在打开下单管理页面: {xiadan_url}")
            webbrowser.open(xiadan_url)
            
        except Exception as e:
            print(f"自动打开浏览器失败: {str(e)}")
    
    # 在后台线程中打开浏览器
    browser_thread = threading.Thread(target=open_pages, daemon=True)
    browser_thread.start()

# 获取今日日期
def get_current_date():
    """获取当前日期，格式为YYYYMMDD"""
    return datetime.datetime.now().strftime('%Y%m%d')

def get_pdf_absolute_path():
    """获取PDF文件夹的绝对路径"""
    # 优先检查测试配置文件
    test_config_file = os.path.join(base_dir, "变量-pdf绝对路径-测试.txt")
    config_file = os.path.join(base_dir, "变量-pdf绝对路径.txt")
    
    # 如果测试文件存在，使用测试文件
    if os.path.exists(test_config_file):
        try:
            with open(test_config_file, 'r', encoding='utf-8') as f:
                first_line = f.readline().strip()
                if first_line:
                    # 检查是否是UNC路径，如果是，替换IP为当前本地IP
                    if first_line.startswith('\\\\'):
                        # 获取当前本地IP
                        current_ip = get_local_ip()
                        
                        # 解析UNC路径，提取IP和后续路径
                        parts = first_line.split('\\')
                        if len(parts) >= 4:  # \\IP\share\path...
                            old_ip = parts[2]  # 第三个部分是IP
                            
                            # 如果IP不同，替换为当前IP
                            if old_ip != current_ip:
                                print(f"检测到IP变化: {old_ip} -> {current_ip}")
                                # 重建UNC路径
                                new_path = f"\\\\{current_ip}\\" + "\\".join(parts[3:])
                                print(f"使用测试PDF路径(IP已更新): {new_path}")
                                return new_path
                    
                    print(f"使用测试PDF路径: {first_line}")
                    return first_line
        except Exception as e:
            print(f"读取测试PDF路径配置失败: {str(e)}")
    
    # 使用正式配置文件
    if os.path.exists(config_file):
        try:
            with open(config_file, 'r', encoding='utf-8') as f:
                first_line = f.readline().strip()
                if first_line:
                    print(f"使用正式PDF路径: {first_line}")
                    return first_line
        except Exception as e:
            print(f"读取PDF路径配置失败: {str(e)}")
    
    # 如果配置文件不存在或读取失败，回退到默认路径
    default_path = os.path.join(base_dir, 'pdf')
    print(f"使用默认PDF路径: {default_path}")
    return default_path

def get_task_folder():
    """获取今天的任务文件夹路径"""
    日期 = get_current_date()
    pdf_base_path = get_pdf_absolute_path()
    return os.path.join(pdf_base_path, 日期)

# 获取操作日志文件路径
def get_log_file_path():
    """获取操作日志文件的完整路径"""
    return os.path.join(base_dir, 操作日志文件名)

# 确保操作日志文件存在并有表头
def ensure_log_file_exists():
    """确保操作日志文件存在，如果不存在则创建并写入表头"""
    log_file = get_log_file_path()
    if not os.path.exists(log_file):
        try:
            with open(log_file, 'w', encoding='utf-8', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(操作日志表头)
            print(f"已创建操作日志文件: {log_file}")
        except Exception as e:
            print(f"创建操作日志文件失败: {str(e)}")
    else:
        # 检查表头是否正确，不正确则尝试修复
        try:
            with open(log_file, 'r', encoding='utf-8', newline='') as f:
                reader = csv.reader(f)
                header = next(reader, None)
                if header != 操作日志表头:
                    # 如果表头不匹配，可以考虑备份旧文件并重新创建，或者提示用户
                    print(f"警告: 操作日志文件 {log_file} 表头不正确或不存在。期望表头: {操作日志表头}, 实际表头: {header}")
                    # 为简单起见，这里仅打印警告。在实际应用中可能需要更复杂的处理。
                    # 或者，强制重写表头（如果文件为空或只有错误表头）
                    if header is None or os.path.getsize(log_file) < 100: # 小于100字节大概率是空或只有表头
                        with open(log_file, 'w', encoding='utf-8', newline='') as fw:
                            writer = csv.writer(fw)
                            writer.writerow(操作日志表头)
                        print(f"已修复操作日志文件表头: {log_file}")

        except Exception as e:
            print(f"检查操作日志文件表头时出错: {str(e)}")

# 记录操作日志
def log_operation(name, pdf_name, operation_type):
    """记录一条操作日志到CSV文件（顶部插入）"""
    ensure_log_file_exists() # 确保文件和表头存在
    log_file = get_log_file_path()
    now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    new_row = [name, now, pdf_name, operation_type]
    
    try:
        rows = []
        with open(log_file, 'r', encoding='utf-8', newline='') as f:
            reader = csv.reader(f)
            header = next(reader) # 读取表头
            rows = list(reader)   # 读取剩余所有行
            
        with open(log_file, 'w', encoding='utf-8', newline='') as f:
            writer = csv.writer(f)
            writer.writerow(header)    # 写回表头
            writer.writerow(new_row)   # 写入新行
            writer.writerows(rows)     # 写回旧行
        return True
    except Exception as e:
        print(f"记录操作日志失败: {str(e)}")
        return False

# 从日志加载操作员姓名
def load_operator_names_from_log():
    """从操作日志CSV文件中加载所有不重复的姓名"""
    ensure_log_file_exists() # 确保文件存在
    log_file = get_log_file_path()
    names = set()
    try:
        with open(log_file, 'r', encoding='utf-8', newline='') as f:
            reader = csv.reader(f)
            header = next(reader, None) # 跳过表头
            if header == 操作日志表头: # 确保表头正确
                for row in reader:
                    if len(row) > 0 and row[0]: # 姓名列不为空
                        names.add(row[0])
        return sorted(list(names))
    except Exception as e:
        print(f"从日志加载操作员姓名失败: {str(e)}")
        return []

# 全局变量，仅作为初始值
日期 = get_current_date()
今天任务文件夹 = get_task_folder()

# 加载客户备注
def load_customer_notes():
    """加载客户备注数据"""
    task_folder = get_task_folder()
    notes_file = os.path.join(task_folder, "数据库.csv")
    customer_notes = {}
    
    if os.path.exists(notes_file):
        try:
            with open(notes_file, 'r', encoding='utf-8', newline='') as f:
                csv_reader = csv.reader(f)
                for row in csv_reader:
                    if len(row) >= 2:
                        customer_id = row[0]
                        note = row[1]
                        customer_notes[customer_id] = note
        except Exception as e:
            print(f"加载客户备注失败: {str(e)}")
            return {}
    return customer_notes

# 加载制作人信息
def load_makers_info():
    """加载制作人信息数据"""
    task_folder = get_task_folder()
    makers_file = os.path.join(task_folder, "制作人.csv")
    makers_info = {}
    
    if os.path.exists(makers_file):
        try:
            with open(makers_file, 'r', encoding='utf-8', newline='') as f:
                csv_reader = csv.reader(f)
                for row in csv_reader:
                    if len(row) >= 2:
                        pdf_name = row[0]
                        maker_name = row[1]
                        makers_info[pdf_name] = maker_name
        except Exception as e:
            print(f"加载制作人信息失败: {str(e)}")
            return {}
    return makers_info

# 保存制作人信息
def save_maker_info(pdf_name, maker_name):
    """保存制作人信息数据"""
    task_folder = get_task_folder()
    makers_file = os.path.join(task_folder, "制作人.csv")
    
    # 加载现有数据
    makers_info = load_makers_info()
    
    # 更新数据
    makers_info[pdf_name] = maker_name
    
    try:
        with open(makers_file, 'w', encoding='utf-8', newline='') as f:
            csv_writer = csv.writer(f)
            for pdf, maker in makers_info.items():
                csv_writer.writerow([pdf, maker])
        return True
    except Exception as e:
        print(f"保存制作人信息失败: {str(e)}")
        return False

# 获取所有制作人姓名
def get_all_maker_names():
    """获取所有制作人姓名列表(去重)"""
    makers_info = load_makers_info()
    unique_names = set(makers_info.values())
    return sorted(list(unique_names))

# 保存客户备注
def save_customer_notes(notes):
    """保存客户备注数据"""
    task_folder = get_task_folder()
    notes_file = os.path.join(task_folder, "数据库.csv")
    try:
        with open(notes_file, 'w', encoding='utf-8', newline='') as f:
            csv_writer = csv.writer(f)
            for customer_id, note in notes.items():
                csv_writer.writerow([customer_id, note])
        return True
    except Exception as e:
        print(f"保存客户备注失败: {str(e)}")
        return False

# 创建今日质检不合格记录文件夹
def get_qa_folders():
    """获取质检不合格记录文件夹路径"""
    日期 = get_current_date()
    质检不合格记录文件夹 = os.path.join(base_dir, "质检不合格照片", 日期)
    质检不合格记录缩略图文件夹 = os.path.join(质检不合格记录文件夹, "缩略图")
    os.makedirs(质检不合格记录文件夹, exist_ok=True)
    os.makedirs(质检不合格记录缩略图文件夹, exist_ok=True)
    return 质检不合格记录文件夹, 质检不合格记录缩略图文件夹

# 初始全局变量，但后续应该使用get_qa_folders()函数获取最新路径
质检不合格记录文件夹, 质检不合格记录缩略图文件夹 = get_qa_folders()

# 初始化 Flask 应用
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 质检不合格记录文件夹
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 限制上传文件大小为16MB

# 设置www目录为模板文件夹
app.template_folder = os.path.join(base_dir, 'www')

# 数据库初始化
def init_database():
    """初始化SQLite数据库"""
    db_path = os.path.join(base_dir, '数据库.db')
    conn = sqlite3.connect(db_path)
    
    try:
        # 创建单价表
        conn.execute('''
            CREATE TABLE IF NOT EXISTS danjia (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                客户名称 TEXT NOT NULL UNIQUE,
                单价 REAL NOT NULL
            )
        ''')
        
        # 检查dengji表是否需要迁移
        cursor = conn.cursor()
        cursor.execute("PRAGMA table_info(dengji)")
        columns = cursor.fetchall()
        
        # 检查是否存在旧的唯一约束
        cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='dengji'")
        table_sql = cursor.fetchone()
        
        if table_sql and "客户编号 TEXT NOT NULL UNIQUE" in table_sql[0]:
            print("检测到旧的dengji表结构，正在迁移...")
            
            # 备份现有数据
            cursor.execute("SELECT * FROM dengji")
            existing_data = cursor.fetchall()
            
            # 删除旧表
            cursor.execute("DROP TABLE dengji")
            
            # 创建新表
            conn.execute('''
                CREATE TABLE dengji (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    客户编号 TEXT NOT NULL,
                    客户名称 TEXT NOT NULL,
                    登记日期 TEXT NOT NULL,
                    UNIQUE(客户编号, 登记日期)
                )
            ''')
            
            # 恢复数据
            for row in existing_data:
                try:
                    cursor.execute(
                        "INSERT INTO dengji (id, 客户编号, 客户名称, 登记日期) VALUES (?, ?, ?, ?)",
                        row
                    )
                except sqlite3.IntegrityError:
                    # 如果有重复数据，跳过
                    print(f"跳过重复数据: {row}")
                    continue
            
            print("dengji表迁移完成")
        else:
            # 创建登记表（新结构）
            conn.execute('''
                CREATE TABLE IF NOT EXISTS dengji (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    客户编号 TEXT NOT NULL,
                    客户名称 TEXT NOT NULL,
                    登记日期 TEXT NOT NULL,
                    UNIQUE(客户编号, 登记日期)
                )
            ''')
        
        # 创建配置表
        conn.execute('''
            CREATE TABLE IF NOT EXISTS config (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                key TEXT UNIQUE NOT NULL,
                value TEXT NOT NULL,
                description TEXT,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        
        # 初始化订单表（通过模块）
        xiadan.init_orders_database(conn)
        
        # 插入初始配置数据（仅在首次创建时）
        cursor = conn.cursor()
        cursor.execute("SELECT COUNT(*) FROM config")
        config_count = cursor.fetchone()[0]
        
        if config_count == 0:
            initial_configs = [
                ('public_url', 'http://122.51.35.105:80', '公网地址'),
                ('fetch_interval', '5', '获取间隔（秒）'),
                ('system_name', '角标识别任务系统', '系统名称'),
                ('max_retries', '3', '最大重试次数'),
                ('timeout_seconds', '30', '请求超时时间（秒）')
            ]
            
            cursor.executemany(
                "INSERT INTO config (key, value, description) VALUES (?, ?, ?)",
                initial_configs
            )
            print("已插入初始配置数据")
        
        conn.commit()
        print("数据库初始化完成")
    except Exception as e:
        print(f"数据库初始化失败: {str(e)}")
    finally:
        conn.close()

# 数据库连接函数
def get_db_connection():
    """获取数据库连接"""
    db_path = os.path.join(base_dir, '数据库.db')
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    return conn

# 初始化数据库
init_database()

# 锁文件管理
lock_files = {
    '1': os.path.join(base_dir, 'lock_1.txt'),
    '2': os.path.join(base_dir, 'lock_2.txt'),
    '3': os.path.join(base_dir, 'lock_3.txt'),
    '4': os.path.join(base_dir, 'lock_4.txt'),
    '5': os.path.join(base_dir, 'lock_5.txt'),
    '6': os.path.join(base_dir, 'lock_6.txt')
}

# 文件锁对象
file_locks = {
    '1': threading.Lock(),
    '2': threading.Lock(),
    '3': threading.Lock(),
    '4': threading.Lock(),
    '5': threading.Lock(),
    '6': threading.Lock()
}

# 获取下一个编号
def get_next_number(prefix):
    """获取指定前缀的下一个编号"""
    lock_file = lock_files[prefix]
    
    # 确保锁文件存在
    if not os.path.exists(lock_file):
        with open(lock_file, 'w') as f:
            f.write('0')
    
    with file_locks[prefix]:
        try:
            # 读取当前编号
            with open(lock_file, 'r') as f:
                current_num = int(f.read().strip() or '0')
            
            # 递增编号
            next_num = current_num + 1
            
            # 写回文件
            with open(lock_file, 'w') as f:
                f.write(str(next_num))
            
            # 格式化为3位数字
            return f"{prefix}{next_num:02d}"
            
        except Exception as e:
            print(f"获取编号失败: {str(e)}")
            return None

# 全局数据存储
data_lock = threading.Lock()
pdf_status_data = []
customer_status_data = {}
all_patterns_data = []

def compress_png(image_path, target_size_kb=5):
    """
    压缩PNG图片到目标大小
    
    参数:
    image_path: 图片路径
    target_size_kb: 目标大小，单位KB
    
    返回:
    bool: 是否压缩成功
    """
    # 获取原始文件大小
    original_size = os.path.getsize(image_path) / 1024  # KB
    
    # 如果文件大小已经在目标大小左右（±1KB），则不需要压缩
    if abs(original_size - target_size_kb) <= 1:
        print(f"文件大小已经接近目标大小，跳过: {image_path} ({original_size:.1f}KB)")
        return True
    
    # 如果文件大小已经小于目标大小，也不需要压缩
    if original_size < target_size_kb:
        print(f"文件大小已经小于目标大小，跳过: {image_path} ({original_size:.1f}KB)")
        return True
    
    try:
        print(f"压缩图片: {image_path} ({original_size:.1f}KB -> 目标 {target_size_kb}KB)")
        
        # 打开图片
        img = Image.open(image_path)
        
        # 保存原始尺寸
        original_width, original_height = img.size
        
        # 尝试1: 使用优化选项保存PNG
        temp_output = BytesIO()
        img.save(temp_output, format='PNG', optimize=True)
        optimized_size = len(temp_output.getvalue()) / 1024  # KB
        
        # 如果优化已足够，保存并返回
        if optimized_size <= target_size_kb:
            with open(image_path, 'wb') as f:
                f.write(temp_output.getvalue())
            print(f"优化成功: {image_path} ({original_size:.1f}KB -> {optimized_size:.1f}KB)")
            return True
        
        # 尝试2: 降低颜色深度
        if img.mode == 'RGBA':
            img_p = img.convert('P', palette=Image.ADAPTIVE, colors=256)
        elif img.mode != 'P':
            img_p = img.convert('P', palette=Image.ADAPTIVE, colors=256)
        else:
            img_p = img.copy()
        
        temp_output = BytesIO()
        img_p.save(temp_output, format='PNG', optimize=True)
        optimized_size = len(temp_output.getvalue()) / 1024  # KB
        
        # 如果优化已足够，保存并返回
        if optimized_size <= target_size_kb:
            with open(image_path, 'wb') as f:
                f.write(temp_output.getvalue())
            print(f"降低颜色深度成功: {image_path} ({original_size:.1f}KB -> {optimized_size:.1f}KB)")
            return True
        
        # 尝试3: 调整图片尺寸
        # 计算需要的压缩比例
        compression_ratio = math.sqrt(target_size_kb / optimized_size)
        new_width = max(int(original_width * compression_ratio), 100)  # 最小宽度100px
        new_height = max(int(original_height * compression_ratio), 100)  # 最小高度100px
        
        # 调整图片尺寸
        resized_img = img.resize((new_width, new_height), Image.LANCZOS)
        
        # 如果是RGBA图片，需要保留透明度
        if img.mode == 'RGBA':
            temp_output = BytesIO()
            resized_img.save(temp_output, format='PNG', optimize=True)
        else:
            # 尝试降低颜色深度
            resized_img_p = resized_img.convert('P', palette=Image.ADAPTIVE, colors=256)
            temp_output = BytesIO()
            resized_img_p.save(temp_output, format='PNG', optimize=True)
        
        final_size = len(temp_output.getvalue()) / 1024  # KB
        
        # 保存最终结果
        with open(image_path, 'wb') as f:
            f.write(temp_output.getvalue())
        
        print(f"压缩完成: {image_path} ({original_size:.1f}KB -> {final_size:.1f}KB)")
        print(f"  - 尺寸调整: {original_width}x{original_height} -> {new_width}x{new_height}")
        
        return True
    except Exception as e:
        print(f"压缩图片失败: {image_path}, 错误: {str(e)}")
        return False

def scan_directory():
    """扫描目录并更新数据"""
    global pdf_status_data, customer_status_data, all_patterns_data
    
    # 获取最新的日期和任务文件夹
    日期 = get_current_date()
    今天任务文件夹 = get_task_folder()
    
    # 检查今天任务文件夹是否存在
    if not os.path.exists(今天任务文件夹):
        print(f"今天任务文件夹不存在: {今天任务文件夹}")
        # 如果今天的文件夹不存在，尝试创建它
        try:
            os.makedirs(今天任务文件夹, exist_ok=True)
            print(f"已创建今天任务文件夹: {今天任务文件夹}")
        except Exception as e:
            print(f"创建今天任务文件夹失败: {str(e)}")
        return
    
    # 加载客户备注数据
    customer_notes = load_customer_notes()
    
    # 初始化：删除所有客户编号为0的PNG文件
    # 获取所有PDF文件夹（子文件夹）
    pdf_folders = [f for f in os.listdir(今天任务文件夹) 
                  if os.path.isdir(os.path.join(今天任务文件夹, f)) and not f.startswith('.')]
    # 遍历PDF文件夹，删除客户编号为0的PNG文件
    删除文件计数 = 0
    for pdf_folder in pdf_folders:
        pdf_path = os.path.join(今天任务文件夹, pdf_folder)
        # 检查各状态子文件夹
        for status_folder in ["没制作", "制作中", "制作好", "质检不合格"]:
            status_path = os.path.join(pdf_path, status_folder)
            if os.path.exists(status_path):
                for pattern_file in [f for f in os.listdir(status_path) if f.endswith('.png')]:
                    # 解析文件名，检查是否客户编号为0
                    try:
                        parts = pattern_file.rsplit('_', 2)
                        if len(parts) >= 2 and parts[-2] == "0":
                            # 删除客户编号为0的PNG文件
                            os.remove(os.path.join(status_path, pattern_file))
                            删除文件计数 += 1
                    except Exception as e:
                        print(f"处理文件名时出错: {pattern_file}, 错误: {str(e)}")
    if 删除文件计数 > 0:
        print(f"已删除 {删除文件计数} 个客户编号为0的PNG文件")
    
    # 清空之前的数据，确保不会保留旧数据
    new_pdf_status = []
    new_customer_status = {}
    new_all_patterns = []
    
    # 获取所有PDF文件夹（子文件夹）
    pdf_folders = [f for f in os.listdir(今天任务文件夹) 
                  if os.path.isdir(os.path.join(今天任务文件夹, f)) and not f.startswith('.')]
    
    print(f"开始扫描今天任务文件夹: {今天任务文件夹}")
    print(f"找到的PDF文件夹: {pdf_folders}")
    
    # 收集所有客户ID
    all_customer_ids = set()
    
    # 遍历PDF文件夹
    for pdf_folder in pdf_folders:
        pdf_path = os.path.join(今天任务文件夹, pdf_folder)
        
        # 检查状态子文件夹
        没制作文件夹 = os.path.join(pdf_path, "没制作")
        制作中文件夹 = os.path.join(pdf_path, "制作中")
        制作好文件夹 = os.path.join(pdf_path, "制作好")
        质检不合格文件夹 = os.path.join(pdf_path, "质检不合格")
        
        # 确保所有状态文件夹存在
        for folder in [没制作文件夹, 制作中文件夹, 制作好文件夹, 质检不合格文件夹]:
            os.makedirs(folder, exist_ok=True)
        
        # 获取各状态文件夹中的文件数量
        没制作文件数 = len([f for f in os.listdir(没制作文件夹) if f.endswith('.png')])
        制作中文件数 = len([f for f in os.listdir(制作中文件夹) if f.endswith('.png')])
        制作好文件数 = len([f for f in os.listdir(制作好文件夹) if f.endswith('.png')])
        质检不合格文件数 = len([f for f in os.listdir(质检不合格文件夹) if f.endswith('.png')])
        
        # 打印每个文件夹中的文件数量，用于调试
        # print(f"PDF: {pdf_folder}")
        # print(f"  没制作: {没制作文件数}")
        # print(f"  制作中: {制作中文件数}")
        # print(f"  制作好: {制作好文件数}")
        # print(f"  质检不合格: {质检不合格文件数}")
        
        # 确定PDF制作状态
        pdf_status = "没制作"
        if 质检不合格文件数 > 0:
            pdf_status = "质检不合格"
        elif 制作好文件数 > 0:
            pdf_status = "制作好"
        elif 制作中文件数 > 0:
            pdf_status = "制作中"
        
        # 添加到PDF状态数据
        new_pdf_status.append({
            "pdf_name": pdf_folder,
            "status": pdf_status,
            "not_started": 没制作文件数,
            "in_progress": 制作中文件数,
            "completed": 制作好文件数,
            "failed_qa": 质检不合格文件数,
            "total": 没制作文件数 + 制作中文件数 + 制作好文件数 + 质检不合格文件数
        })
        
        # 收集并处理各状态文件夹中的图案
        for status_folder, status in [
            (没制作文件夹, "没制作"),
            (制作中文件夹, "制作中"),
            (制作好文件夹, "制作好"),
            (质检不合格文件夹, "质检不合格")
        ]:
            if os.path.exists(status_folder):
                pattern_files = [f for f in os.listdir(status_folder) if f.endswith('.png')]
                
                # 打印每个状态文件夹中的所有文件，用于调试
                if len(pattern_files) > 0:
                    # print(f"  {status} 文件夹中的文件: {pattern_files}")
                    pass
                
                for pattern_file in pattern_files:
                    # 解析文件名，格式为: PDF文件名_客户编号_序号.png
                    try:
                        parts = pattern_file.rsplit('_', 2)
                        if len(parts) >= 2:
                            # 处理客户编号
                            customer_id = parts[-2]
                            
                            # 忽略客户编号为0的记录
                            if customer_id == "0":
                                continue
                                
                            all_customer_ids.add(customer_id)
                            
                            # 创建图案数据对象
                            pattern_data = {
                                "pdf_name": pdf_folder,  # 使用文件夹名称作为PDF名称
                                "customer_id": customer_id,
                                "sequence": parts[-1].split('.')[0],
                                "filename": pattern_file,
                                "status": status,
                                "full_path": os.path.join(status_folder.replace(今天任务文件夹, "").lstrip(os.sep), pattern_file)
                            }
                            
                            # 添加到所有图案列表
                            new_all_patterns.append(pattern_data)
                            
                            # 如果这是新客户，初始化客户状态
                            if customer_id not in new_customer_status:
                                new_customer_status[customer_id] = {
                                    "total": 0,
                                    "not_started": 0,
                                    "in_progress": 0,
                                    "completed": 0,
                                    "failed_qa": 0,
                                    "patterns": [],
                                    "note": customer_notes.get(customer_id, "")  # 添加备注数据
                                }
                            
                            # 更新客户状态统计
                            customer_data = new_customer_status[customer_id]
                            customer_data["total"] += 1
                            
                            if status == "没制作":
                                customer_data["not_started"] += 1
                            elif status == "制作中":
                                customer_data["in_progress"] += 1
                            elif status == "制作好":
                                customer_data["completed"] += 1
                            elif status == "质检不合格":
                                customer_data["failed_qa"] += 1
                            
                            # 添加图案到客户数据
                            customer_data["patterns"].append(pattern_data)
                    except Exception as e:
                        print(f"处理文件名时出错: {pattern_file}, 错误: {str(e)}")
    
    # 使用锁确保线程安全更新全局数据
    with data_lock:
        pdf_status_data = new_pdf_status
        customer_status_data = new_customer_status
        all_patterns_data = new_all_patterns
    
    print(f"数据更新完成: {len(pdf_status_data)} 个PDF, {len(customer_status_data)} 个客户, {len(all_patterns_data)} 个图案")
    print("")

def update_data_periodically():
    """定期更新数据的后台线程"""
    folder_scan_counter = 0
    while True:
        try:
            scan_directory()
            
            # xiadan模块现在有自己的0.5秒高频扫描，这里不再调用
            # 避免冲突和重复扫描
            folder_scan_counter += 1
            if folder_scan_counter >= 12:
                # 只打印日志，不再调用xiadan扫描
                print(f"⏰ [INFO] 任务统计模块扫描完成 (xiadan模块有独立的0.5秒扫描)")
                folder_scan_counter = 0  # 重置计数器
            
        except Exception as e:
            print(f"更新数据时出错: {str(e)}")
        time.sleep(5)  # 每5秒更新一次

# 压缩图片函数
def compress_image(image_data, max_size_kb=300):
    """压缩图片到指定大小"""
    img = Image.open(BytesIO(image_data))
    
    # 创建缩略图
    if img.width > 150 or img.height > 150:
        img.thumbnail((150, 150), Image.LANCZOS)
    
    # 保存缩略图
    thumb_buffer = BytesIO()
    img.save(thumb_buffer, format='JPEG', quality=85)
    thumbnail_data = thumb_buffer.getvalue()
    
    # 重新打开原图进行压缩
    img = Image.open(BytesIO(image_data))
    
    # 初始质量
    quality = 85
    output_buffer = BytesIO()
    img.save(output_buffer, format='JPEG', quality=quality)
    
    # 如果大小超过限制，降低质量直到满足要求
    while output_buffer.tell() > max_size_kb * 1024 and quality > 10:
        output_buffer = BytesIO()
        quality -= 5
        img.save(output_buffer, format='JPEG', quality=quality)
    
    return output_buffer.getvalue(), thumbnail_data

# 路由设置
@app.route('/')
def index():
    """主页"""
    ensure_log_file_exists() # 确保日志文件在应用启动时被检查或创建
    return render_template('dashboard.html')

@app.route('/baogong')
def baogong():
    """报工页面"""
    ensure_log_file_exists() # 确保日志文件被检查或创建
    operator_names = load_operator_names_from_log() # 加载操作员姓名列表
    return render_template('baogong.html', operator_names=operator_names)

@app.route('/api/data')
def get_data():
    """获取所有数据的API"""
    ensure_log_file_exists() # 确保日志文件被检查或创建
    with data_lock:
        return jsonify({
            "pdf_status": pdf_status_data,
            "customer_status": customer_status_data
        })

@app.route('/api/pdf_status')
def get_pdf_status():
    """获取PDF状态数据的API"""
    with data_lock:
        return jsonify(pdf_status_data)

@app.route('/api/customer_status')
def get_customer_status():
    """获取客户状态数据的API"""
    with data_lock:
        return jsonify(customer_status_data)

@app.route('/api/patterns')
def get_patterns():
    """获取所有图案数据的API"""
    with data_lock:
        return jsonify(all_patterns_data)

@app.route('/api/status/<pdf_name>', methods=['POST'])
def update_status(pdf_name):
    """更新PDF状态的API"""
    ensure_log_file_exists() # 确保日志文件被检查或创建
    # 获取最新的任务文件夹
    今天任务文件夹 = get_task_folder()
    
    data = request.json
    action = data.get('action')
    operator_name = data.get('operator_name', '未知用户') # 从请求中获取操作员姓名，默认为'未知用户'
    
    pdf_path = os.path.join(今天任务文件夹, pdf_name)
    
    # 确保PDF文件夹存在
    if not os.path.exists(pdf_path):
        return jsonify({"success": False, "message": f"PDF文件夹不存在: {pdf_name}"}), 404
    
    # 根据操作执行不同的文件移动
    if action == "start":
        # 开始制作：从没制作移动到制作中
        source_folder = os.path.join(pdf_path, "没制作")
        target_folder = os.path.join(pdf_path, "制作中")
        log_operation(operator_name, pdf_name, "开始") # 记录日志
    elif action == "complete":
        # 制作完成：从制作中移动到制作好
        source_folder = os.path.join(pdf_path, "制作中")
        target_folder = os.path.join(pdf_path, "制作好")
        log_operation(operator_name, pdf_name, "完成") # 记录日志
    else:
        return jsonify({"success": False, "message": "不支持的操作"}), 400
    
    # 确保源文件夹和目标文件夹存在
    if not os.path.exists(source_folder) or not os.path.exists(target_folder):
        return jsonify({"success": False, "message": "状态文件夹不存在"}), 404
    
    # 移动文件
    moved_count = 0
    for file_name in os.listdir(source_folder):
        if file_name.endswith('.png'):
            source_file = os.path.join(source_folder, file_name)
            target_file = os.path.join(target_folder, file_name)
            shutil.move(source_file, target_file)
            moved_count += 1
    
    # 触发数据更新
    scan_directory()
    
    return jsonify({
        "success": True, 
        "message": f"成功将 {moved_count} 个文件从 {action} 状态更新"
    })

@app.route('/api/qa_report/<pdf_name>', methods=['POST'])
def qa_report(pdf_name):
    """质检报工API，处理不合格图案和照片上传"""
    ensure_log_file_exists() # 确保日志文件被检查或创建
    # 获取最新的任务文件夹
    今天任务文件夹 = get_task_folder()
    # 获取最新的质检不合格记录文件夹
    质检不合格记录文件夹, 质检不合格记录缩略图文件夹 = get_qa_folders()
    
    failed_patterns = request.form.getlist('failed_patterns[]')
    operator_name = request.form.get('operator_name', '未知用户') # 从请求中获取操作员姓名
    
    pdf_path = os.path.join(今天任务文件夹, pdf_name)
    
    # 确保PDF文件夹存在
    if not os.path.exists(pdf_path):
        return jsonify({"success": False, "message": f"PDF文件夹不存在: {pdf_name}"}), 404
    
    制作好文件夹 = os.path.join(pdf_path, "制作好")
    质检不合格文件夹 = os.path.join(pdf_path, "质检不合格")
    
    # 确保文件夹存在
    os.makedirs(制作好文件夹, exist_ok=True)
    os.makedirs(质检不合格文件夹, exist_ok=True)
    
    # 获取所有"制作好"和"质检不合格"文件夹中的图案
    制作好图案 = [f for f in os.listdir(制作好文件夹) if f.endswith('.png')]
    质检不合格图案 = [f for f in os.listdir(质检不合格文件夹) if f.endswith('.png')]
    
    # 移动图案
    移到不合格数量 = 0
    移到制作好数量 = 0
    
    # 处理"制作好"文件夹中的图案
    for pattern_name in 制作好图案:
        if pattern_name in failed_patterns:
            # 如果在不合格列表中，移动到"质检不合格"文件夹
            source_file = os.path.join(制作好文件夹, pattern_name)
            target_file = os.path.join(质检不合格文件夹, pattern_name)
            if os.path.exists(source_file) and not os.path.exists(target_file):
                shutil.move(source_file, target_file)
                移到不合格数量 += 1
    
    # 处理"质检不合格"文件夹中的图案
    for pattern_name in 质检不合格图案:
        if pattern_name not in failed_patterns:
            # 如果不在不合格列表中，移回"制作好"文件夹
            source_file = os.path.join(质检不合格文件夹, pattern_name)
            target_file = os.path.join(制作好文件夹, pattern_name)
            if os.path.exists(source_file) and not os.path.exists(target_file):
                shutil.move(source_file, target_file)
                移到制作好数量 += 1
    
    # 处理上传的照片
    上传数量 = 0
    upload_files = request.files.getlist('photos[]')
    
    # 构建正常的质检不合格照片文件夹路径(与质检不合格文件夹同级)
    质检不合格照片路径 = os.path.join(pdf_path, "质检不合格照片")
    os.makedirs(质检不合格照片路径, exist_ok=True)
    
    # 获取现有照片的最大序号
    现有照片 = [f for f in os.listdir(质检不合格照片路径) if f.endswith(('.jpg', '.jpeg', '.png'))]
    最大序号 = 0
    
    # 提取现有文件的序号
    for 照片 in 现有照片:
        try:
            # 假设文件名格式为 数字.扩展名
            序号 = int(os.path.splitext(照片)[0])
            最大序号 = max(最大序号, 序号)
        except ValueError:
            # 如果文件名不是纯数字，忽略
            continue
    
    # 上传新照片时从最大序号开始递增
    for file in upload_files:
        if file and file.filename:
            最大序号 += 1
            file_extension = os.path.splitext(file.filename)[1]
            if not file_extension:
                file_extension = '.jpg'
            
            # 使用递增的数字作为文件名
            filename = f"{最大序号}{file_extension}"
            
            # 压缩图片并保存原图和缩略图
            try:
                image_data = file.read()
                compressed_image, thumbnail = compress_image(image_data, max_size_kb=500)  # 压缩到500KB
                
                # 保存到质检不合格记录文件夹
                # with open(os.path.join(质检不合格记录文件夹, filename), 'wb') as f:
                #     f.write(compressed_image)
                
                # 保存缩略图
                # with open(os.path.join(质检不合格记录缩略图文件夹, filename), 'wb') as f:
                #     f.write(thumbnail)
                
                # 保存到PDF文件夹下的质检不合格照片文件夹
                with open(os.path.join(质检不合格照片路径, filename), 'wb') as f:
                    f.write(compressed_image)
                
                上传数量 += 1
            except Exception as e:
                print(f"处理图片时出错: {str(e)}")
    
    # 触发数据更新
    scan_directory()
    
    # 记录质检操作日志
    log_operation(operator_name, pdf_name, "质检")
    
    # 使用勾选的总图片数量，而不是新移动的数量
    return jsonify({
        "success": True,
        "message": f"质检报工成功: {len(failed_patterns)} 个图案不合格，上传了 {上传数量} 张照片"
    })

@app.route('/images/<path:filename>')
def serve_image(filename):
    """提供图像文件"""
    try:
        # 获取最新的任务文件夹
        今天任务文件夹 = get_task_folder()
        
        print(f"请求图像: {filename}")
        
        # 判断是否是完整路径
        if filename.startswith(os.path.basename(今天任务文件夹)):
            # 这是一个完整路径
            parts = filename.split('/')
            if len(parts) >= 2:
                # 构建完整路径
                full_path = os.path.join(base_dir, "pdf", parts[0])
                basename = parts[-1]
                subdir = '/'.join(parts[1:-1])
                if subdir:
                    full_path = os.path.join(full_path, subdir)
                
                if os.path.exists(os.path.join(full_path, basename)):
                    return send_from_directory(full_path, basename)
        
        # 检查是否是PDF文件夹下的"质检不合格照片"路径
        parts = filename.split('/')
        if len(parts) >= 3 and parts[1] == "质检不合格照片":
            # 这是一个PDF专属的质检不合格照片路径
            pdf_name = parts[0]
            photo_name = parts[2]
            pdf_qa_photos_path = os.path.join(今天任务文件夹, pdf_name, "质检不合格照片")
            
            if os.path.exists(os.path.join(pdf_qa_photos_path, photo_name)):
                return send_from_directory(pdf_qa_photos_path, photo_name)
        
        # 使用原始方法尝试
        directory = os.path.dirname(filename)
        basename = os.path.basename(filename)
        
        # 构建完整路径
        full_path = os.path.join(今天任务文件夹, directory)
        print(f"查找图像路径: {full_path}")
        
        if os.path.exists(os.path.join(full_path, basename)):
            return send_from_directory(full_path, basename)
        else:
            print(f"图像不存在: {os.path.join(full_path, basename)}")
            return "图像不存在", 404
    except Exception as e:
        print(f"提供图像时出错: {str(e)}")
        return "服务图像时出错", 500

@app.route('/qa_images/<filename>')
def serve_qa_image(filename):
    """提供质检不合格记录图像"""
    质检不合格记录文件夹, _ = get_qa_folders()
    return send_from_directory(质检不合格记录文件夹, filename)

@app.route('/qa_thumbnails/<filename>')
def serve_qa_thumbnail(filename):
    """提供质检不合格记录缩略图"""
    _, 质检不合格记录缩略图文件夹 = get_qa_folders()
    return send_from_directory(质检不合格记录缩略图文件夹, filename)

@app.route('/static/<path:filename>')
def serve_static(filename):
    """提供静态文件"""
    return send_from_directory('static', filename)

# 添加二维码生成路径
@app.route('/qr')
def generate_qr_redirect():
    """二维码重定向到报工界面"""
    pdf_filename = request.args.get('pdffilename', '')
    return redirect(f'/baogong?pdffilename={pdf_filename}')

# 调试端点，显示当前的数据状态
@app.route('/debug')
def debug_data():
    """调试端点，显示当前的数据状态"""
    with data_lock:
        debug_info = {
            "current_date": 日期,
            "task_folder": 今天任务文件夹,
            "pdf_count": len(pdf_status_data),
            "customer_count": len(customer_status_data),
            "pattern_count": len(all_patterns_data),
            "pdf_status": pdf_status_data,
            "all_patterns": all_patterns_data[:20] if len(all_patterns_data) > 20 else all_patterns_data  # 只显示前20个图案，避免数据过多
        }
        return jsonify(debug_info)

# 重置缓存数据
@app.route('/reset', methods=['POST'])
def reset_cache():
    """重置所有缓存数据并强制刷新"""
    global pdf_status_data, customer_status_data, all_patterns_data
    
    with data_lock:
        pdf_status_data = []
        customer_status_data = {}
        all_patterns_data = []
    
    # 强制扫描目录
    scan_directory()
    
    return jsonify({"success": True, "message": "缓存数据已重置并重新扫描"})

# 新增：图片压缩API
@app.route('/compress_images', methods=['POST'])
def compress_all_images():
    """压缩所有PNG图片到约5KB大小"""
    # 获取最新的任务文件夹
    今天任务文件夹 = get_task_folder()
    
    target_size_kb = int(request.args.get('size', 5))  # 默认5KB，可通过查询参数修改
    
    if not os.path.exists(今天任务文件夹):
        return jsonify({"success": False, "message": f"今天任务文件夹不存在: {今天任务文件夹}"}), 404
    
    compressed_count = 0
    skipped_count = 0
    error_count = 0
    
    # 遍历所有PDF文件夹
    for pdf_folder in [f for f in os.listdir(今天任务文件夹) 
                       if os.path.isdir(os.path.join(今天任务文件夹, f)) and not f.startswith('.')]:
        pdf_path = os.path.join(今天任务文件夹, pdf_folder)
        
        # 遍历所有状态文件夹
        for status_folder in ["没制作", "制作中", "制作好", "质检不合格"]:
            status_path = os.path.join(pdf_path, status_folder)
            
            if os.path.exists(status_path):
                for pattern_file in [f for f in os.listdir(status_path) if f.endswith('.png')]:
                    image_path = os.path.join(status_path, pattern_file)
                    
                    # 获取文件大小
                    file_size = os.path.getsize(image_path) / 1024  # KB
                    
                    # 如果文件大小已经在目标范围内，跳过
                    if file_size <= target_size_kb + 1:  # 允许目标大小+1KB范围内的文件不压缩
                        skipped_count += 1
                        continue
                    
                    # 压缩图片
                    if compress_png(image_path, target_size_kb):
                        compressed_count += 1
                    else:
                        error_count += 1
    
    # 触发数据更新
    scan_directory()
    
    return jsonify({
        "success": True,
        "message": f"图片压缩完成。已压缩: {compressed_count}, 已跳过: {skipped_count}, 错误: {error_count}",
        "details": {
            "compressed": compressed_count,
            "skipped": skipped_count,
            "errors": error_count,
            "target_size": target_size_kb
        }
    })

# 压缩特定PDF文件夹中的图片
@app.route('/compress_pdf_images/<pdf_name>', methods=['POST'])
def compress_pdf_images(pdf_name):
    """压缩特定PDF文件夹中的所有PNG图片"""
    # 获取最新的任务文件夹
    今天任务文件夹 = get_task_folder()
    
    target_size_kb = int(request.args.get('size', 5))  # 默认5KB，可通过查询参数修改
    
    pdf_path = os.path.join(今天任务文件夹, pdf_name)
    
    # 确保PDF文件夹存在
    if not os.path.exists(pdf_path):
        return jsonify({"success": False, "message": f"PDF文件夹不存在: {pdf_name}"}), 404
    
    compressed_count = 0
    skipped_count = 0
    error_count = 0
    
    # 遍历所有状态文件夹
    for status_folder in ["没制作", "制作中", "制作好", "质检不合格"]:
        status_path = os.path.join(pdf_path, status_folder)
        
        if os.path.exists(status_path):
            for pattern_file in [f for f in os.listdir(status_path) if f.endswith('.png')]:
                image_path = os.path.join(status_path, pattern_file)
                
                # 获取文件大小
                file_size = os.path.getsize(image_path) / 1024  # KB
                
                # 如果文件大小已经在目标范围内，跳过
                if file_size <= target_size_kb + 1:  # 允许目标大小+1KB范围内的文件不压缩
                    skipped_count += 1
                    continue
                
                # 压缩图片
                if compress_png(image_path, target_size_kb):
                    compressed_count += 1
                else:
                    error_count += 1
    
    # 触发数据更新
    scan_directory()
    
    return jsonify({
        "success": True,
        "message": f"PDF文件夹'{pdf_name}'图片压缩完成。已压缩: {compressed_count}, 已跳过: {skipped_count}, 错误: {error_count}",
        "details": {
            "pdf": pdf_name,
            "compressed": compressed_count,
            "skipped": skipped_count,
            "errors": error_count,
            "target_size": target_size_kb
        }
    })

# 添加质检不合格照片访问路由
@app.route('/qa_photos/<filename>')
def serve_qa_photos(filename):
    """提供质检不合格照片文件夹中的图像"""
    # 获取最新的任务文件夹
    今天任务文件夹 = get_task_folder()
    
    # 构建全路径文件名
    parts = filename.split('_', 1)  # 假设文件名格式为: pdf名称_照片名称
    if len(parts) >= 2:
        pdf_name = parts[0]
        photo_name = parts[1]
        pdf_path = os.path.join(今天任务文件夹, pdf_name)
        照片路径 = os.path.join(pdf_path, "质检不合格照片", photo_name)
        
        if os.path.exists(照片路径):
            return send_from_directory(os.path.dirname(照片路径), os.path.basename(照片路径))
    
    # 如果找不到特定PDF的照片，返回404
    return "照片不存在", 404

@app.route('/api/qa_photos')
def get_qa_photos():
    """获取质检不合格照片列表"""
    try:
        # 获取最新的任务文件夹
        今天任务文件夹 = get_task_folder()
        
        # 检查是否有特定的PDF名称参数
        pdf_name = request.args.get('pdfName')
        
        if pdf_name:
            # 获取特定PDF文件夹下的质检不合格照片
            pdf_path = os.path.join(今天任务文件夹, pdf_name)
            pdf_qa_photos_path = os.path.join(pdf_path, "质检不合格照片")
            
            if not os.path.exists(pdf_qa_photos_path):
                os.makedirs(pdf_qa_photos_path, exist_ok=True)
                return jsonify([])
            
            # 获取所有图片文件
            photos = [f for f in os.listdir(pdf_qa_photos_path) if f.endswith(('.jpg', '.jpeg', '.png'))]
            
            # 按数字排序（1.jpg, 2.jpg, 10.jpg应该排序为 1.jpg, 2.jpg, 10.jpg而不是按字符串排序)
            photos.sort(key=lambda x: int(os.path.splitext(x)[0]) if os.path.splitext(x)[0].isdigit() else 0)
            
            print(f"返回PDF {pdf_name} 的质检不合格照片: {photos}")
            return jsonify(photos)
        else:
            # 获取所有PDF文件夹的质检不合格照片
            all_photos = []
            
            # 遍历所有PDF文件夹
            for pdf_folder in [f for f in os.listdir(今天任务文件夹) 
                              if os.path.isdir(os.path.join(今天任务文件夹, f)) and not f.startswith('.')]:
                pdf_path = os.path.join(今天任务文件夹, pdf_folder)
                pdf_qa_photos_path = os.path.join(pdf_path, "质检不合格照片")
                
                if os.path.exists(pdf_qa_photos_path):
                    # 获取所有图片文件并加上PDF名称前缀
                    for photo in [f for f in os.listdir(pdf_qa_photos_path) if f.endswith(('.jpg', '.jpeg', '.png'))]:
                        # 尝试从文件名获取数字
                        try:
                            number = int(os.path.splitext(photo)[0]) if os.path.splitext(photo)[0].isdigit() else 0
                        except ValueError:
                            number = 0
                            
                        all_photos.append({
                            "pdf": pdf_folder,
                            "filename": photo,
                            "fullname": f"{pdf_folder}_{photo}",
                            "number": number
                        })
            
            # 按文件名中的数字排序
            all_photos.sort(key=lambda x: (x["pdf"], x["number"]))
            
            # 只返回文件名列表
            return jsonify([photo["fullname"] for photo in all_photos])
    except Exception as e:
        print(f"获取质检不合格照片列表时出错: {str(e)}")
        return jsonify([])

@app.route('/api/save_note', methods=['POST'])
def save_note():
    """保存客户备注"""
    try:
        # 获取最新的任务文件夹
        今天任务文件夹 = get_task_folder()
        
        data = request.json
        customer_id = data.get('customer_id')
        note = data.get('note', '')
        
        if not customer_id:
            return jsonify({"success": False, "message": "缺少客户ID"}), 400
        
        # 确保今天任务文件夹存在
        os.makedirs(今天任务文件夹, exist_ok=True)
        
        # 加载现有备注
        customer_notes = load_customer_notes()
        
        # 更新备注
        customer_notes[customer_id] = note
        
        # 保存备注
        try:
            save_customer_notes(customer_notes)
            
            # 更新内存中的数据
            if customer_id in customer_status_data:
                customer_status_data[customer_id]['note'] = note
            
            return jsonify({
                "success": True,
                "message": "备注保存成功"
            })
        except Exception as e:
            print(f"保存备注文件时出错: {str(e)}")
            return jsonify({
                "success": False,
                "message": f"保存备注文件失败: {str(e)}"
            }), 500
            
    except Exception as e:
        print(f"保存备注时出错: {str(e)}")
        return jsonify({
            "success": False,
            "message": f"保存备注时出错: {str(e)}"
        }), 500

# 添加PDF上传相关的路由
@app.route('/upload')
def upload_page():
    """PDF上传页面"""
    ensure_log_file_exists() # 确保日志文件被检查或创建
    return render_template('upload.html')

@app.route('/api/pdf_files')
def get_pdf_files():
    """获取已存在的PDF文件列表"""
    # 获取最新的任务文件夹
    今天任务文件夹 = get_task_folder()
    
    # 检查今天任务文件夹是否存在
    if not os.path.exists(今天任务文件夹):
        return jsonify([])
    
    # 获取文件夹中的所有PDF文件
    pdf_files = []
    for file in os.listdir(今天任务文件夹):
        if file.lower().endswith('.pdf'):
            file_path = os.path.join(今天任务文件夹, file)
            try:
                # 获取文件创建时间和大小
                created_at = os.path.getctime(file_path)
                file_size = os.path.getsize(file_path)
                
                pdf_files.append({
                    'name': file,
                    'created_at': created_at * 1000,  # 转换为毫秒
                    'size': file_size
                })
            except Exception as e:
                print(f"获取PDF文件信息时出错: {str(e)}")
    
    # 按创建时间降序排序
    pdf_files.sort(key=lambda x: x['created_at'], reverse=True)
    
    return jsonify(pdf_files)

@app.route('/api/upload_pdf', methods=['POST'])
def upload_pdf():
    """上传PDF文件"""
    try:
        # 获取最新的任务文件夹
        今天任务文件夹 = get_task_folder()
        
        # 检查是否有文件上传
        if 'files[]' not in request.files:
            print("错误: 请求中没有找到files[]字段")
            return jsonify({"success": False, "message": "未选择文件", "errors": ["请求中没有包含文件"]}), 400
        
        files = request.files.getlist('files[]')
        
        # 检查是否有文件被选择
        if not files or all(file.filename == '' for file in files):
            print("错误: 未选择任何文件或文件名为空")
            return jsonify({"success": False, "message": "未选择文件", "errors": ["没有选择任何文件"]}), 400
        
        # 记录上传的文件名
        print(f"接收到的文件: {[f.filename for f in files if f.filename]}")
        
        # 创建今天任务文件夹，如果不存在
        try:
            os.makedirs(今天任务文件夹, exist_ok=True)
            print(f"确保今天任务文件夹存在: {今天任务文件夹}")
        except Exception as e:
            print(f"创建今天任务文件夹失败: {str(e)}")
            return jsonify({"success": False, "message": f"创建任务文件夹失败: {str(e)}", "errors": [f"创建文件夹错误: {str(e)}"]}), 500
        
        # 处理每个上传的文件
        uploaded_count = 0
        errors = []
        
        for file in files:
            if not file or not file.filename:
                continue
                
            filename = file.filename
            print(f"处理文件: {filename}")
            
            # 检查是否是PDF文件
            if not filename.lower().endswith('.pdf'):
                error_msg = f"{filename}: 不是有效的PDF文件"
                print(f"错误: {error_msg}")
                errors.append(error_msg)
                continue
            
            # 检查文件名是否以P开头
            if not filename.startswith('P'):
                error_msg = f"{filename}: 文件名必须以'P'开头"
                print(f"错误: {error_msg}")
                errors.append(error_msg)
                continue
            
            # 检查是否已存在同名文件
            file_path = os.path.join(今天任务文件夹, filename)
            
            if os.path.exists(file_path):
                error_msg = f"{filename}: 文件已存在，请勿重复上传"
                print(f"错误: {error_msg}")
                errors.append(error_msg)
                continue
            
            try:
                # 保存PDF文件
                file.save(file_path)
                print(f"保存PDF文件到: {file_path}")
                
                # 检查文件是否成功保存
                if not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
                    raise Exception("文件保存失败或文件大小为0")
                
                uploaded_count += 1
                print(f"文件 {filename} 上传成功")
            except Exception as e:
                error_msg = f"{filename}: 上传失败 - {str(e)}"
                print(f"上传过程中出错: {error_msg}")
                errors.append(error_msg)
                
                # 如果发生错误，尝试删除已上传的文件
                try:
                    if os.path.exists(file_path):
                        os.remove(file_path)
                        print(f"清理已上传的文件: {file_path}")
                except Exception as cleanup_error:
                    print(f"清理文件失败: {str(cleanup_error)}")
        
        # 触发数据更新
        try:
            scan_directory()
            print("数据扫描完成")
        except Exception as e:
            print(f"扫描目录失败: {str(e)}")
        
        # 返回结果
        if uploaded_count > 0:
            success_message = f"成功上传 {uploaded_count} 个PDF文件"
            if errors:
                success_message += f"，但有 {len(errors)} 个文件上传失败"
            
            print(f"上传结果: {success_message}")
            return jsonify({
                "success": True,
                "message": success_message,
                "uploaded": uploaded_count,
                "errors": errors
            })
        else:
            error_message = "所有文件上传失败"
            if errors:
                error_details = "，错误原因: " + "; ".join(errors[:3])
                if len(errors) > 3:
                    error_details += f" 等{len(errors)}个错误"
                error_message += error_details
            
            print(f"上传失败: {error_message}")
            print(f"详细错误: {errors}")
            
            return jsonify({
                "success": False,
                "message": error_message,
                "errors": errors
            }), 400
    except Exception as e:
        error_message = f"处理上传请求时发生意外错误: {str(e)}"
        print(f"上传处理异常: {error_message}")
        import traceback
        print(traceback.format_exc())
        
        return jsonify({
            "success": False,
            "message": error_message,
            "errors": [error_message]
        }), 500

@app.route('/api/maker_names')
def get_maker_names():
    """获取所有制作人姓名的API"""
    try:
        names = get_all_maker_names()
        return jsonify(names)
    except Exception as e:
        print(f"获取制作人姓名时出错: {str(e)}")
        return jsonify([])

@app.route('/api/save_maker', methods=['POST'])
def save_maker():
    """保存制作人信息的API"""
    try:
        data = request.json
        pdf_name = data.get('pdf_name')
        maker_name = data.get('maker_name')
        
        if not pdf_name or not maker_name:
            return jsonify({
                "success": False,
                "message": "缺少PDF名称或制作人姓名"
            }), 400
        
        # 保存制作人信息
        if save_maker_info(pdf_name, maker_name):
            return jsonify({
                "success": True,
                "message": f"制作人信息保存成功: {maker_name}"
            })
        else:
            return jsonify({
                "success": False,
                "message": "制作人信息保存失败"
            }), 500
    except Exception as e:
        print(f"保存制作人信息时出错: {str(e)}")
        return jsonify({
            "success": False,
            "message": f"保存制作人信息时出错: {str(e)}"
        }), 500

# 新增路由：单价管理页面
@app.route('/danjia')
def danjia():
    """单价管理页面"""
    try:
        return render_template('danjia.html')
    except Exception as e:
        print(f"渲染单价管理页面时出错: {str(e)}")
        return f"页面加载错误: {str(e)}", 500

@app.route('/dengji')
def dengji():
    """编号登记页面"""
    try:
        return render_template('dengji.html')
    except Exception as e:
        print(f"渲染编号登记页面时出错: {str(e)}")
        return f"页面加载错误: {str(e)}", 500

# 单价管理 API
@app.route('/api/danjia', methods=['GET'])
def get_danjia():
    """获取单价数据"""
    try:
        conn = get_db_connection()
        cursor = conn.execute('SELECT * FROM danjia ORDER BY 客户名称')
        rows = cursor.fetchall()
        conn.close()
        
        data = []
        for row in rows:
            data.append({
                'id': row['id'],
                '客户名称': row['客户名称'],
                '单价': row['单价']
            })
        
        return jsonify({
            'success': True,
            'data': data
        })
    except Exception as e:
        print(f"获取单价数据时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

@app.route('/api/danjia', methods=['POST'])
def add_danjia():
    """添加单价数据"""
    try:
        data = request.json
        客户名称 = data.get('客户名称', '').strip()
        单价 = float(data.get('单价', 0))
        
        if not 客户名称:
            return jsonify({
                'success': False,
                'error': '客户名称不能为空'
            }), 400
        
        conn = get_db_connection()
        cursor = conn.execute(
            'INSERT INTO danjia (客户名称, 单价) VALUES (?, ?)',
            (客户名称, 单价)
        )
        conn.commit()
        new_id = cursor.lastrowid
        conn.close()
        
        return jsonify({
            'success': True,
            'id': new_id,
            'message': '添加成功'
        })
    except sqlite3.IntegrityError:
        return jsonify({
            'success': False,
            'error': '客户名称已存在'
        }), 400
    except Exception as e:
        print(f"添加单价数据时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

@app.route('/api/danjia', methods=['PUT'])
def update_danjia():
    """更新单价数据"""
    try:
        data = request.json
        id = data.get('id')
        客户名称 = data.get('客户名称', '').strip()
        单价 = float(data.get('单价', 0))
        
        if not id:
            return jsonify({
                'success': False,
                'error': '缺少记录ID'
            }), 400
        
        if not 客户名称:
            return jsonify({
                'success': False,
                'error': '客户名称不能为空'
            }), 400
        
        conn = get_db_connection()
        conn.execute(
            'UPDATE danjia SET 客户名称 = ?, 单价 = ? WHERE id = ?',
            (客户名称, 单价, id)
        )
        conn.commit()
        conn.close()
        
        return jsonify({
            'success': True,
            'message': '更新成功'
        })
    except sqlite3.IntegrityError:
        return jsonify({
            'success': False,
            'error': '客户名称已存在'
        }), 400
    except Exception as e:
        print(f"更新单价数据时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

@app.route('/api/danjia', methods=['DELETE'])
def delete_danjia():
    """删除单价数据"""
    try:
        data = request.json
        id = data.get('id')
        
        if not id:
            return jsonify({
                'success': False,
                'error': '缺少记录ID'
            }), 400
        
        conn = get_db_connection()
        conn.execute('DELETE FROM danjia WHERE id = ?', (id,))
        conn.commit()
        conn.close()
        
        return jsonify({
            'success': True,
            'message': '删除成功'
        })
    except Exception as e:
        print(f"删除单价数据时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

# 获取客户名称列表（供编号登记页面使用）
@app.route('/api/danjia_customers', methods=['GET'])
def get_danjia_customers():
    """获取所有客户名称列表"""
    try:
        conn = get_db_connection()
        cursor = conn.execute('SELECT 客户名称 FROM danjia ORDER BY 客户名称')
        rows = cursor.fetchall()
        conn.close()
        
        customers = [row['客户名称'] for row in rows]
        
        return jsonify({
            'success': True,
            'data': customers
        })
    except Exception as e:
        print(f"获取客户名称列表时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

# 编号登记 API
@app.route('/api/dengji', methods=['GET'])
def get_dengji():
    conn = get_db_connection()
    cursor = conn.cursor()
    
    # 支持多种参数名格式
    query_date = request.args.get('date') or request.args.get('登记日期')
    query_prefix = request.args.get('prefix')
    query_customer_id = request.args.get('客户编号')

    sql_query = "SELECT id, 客户编号, 客户名称, 登记日期 FROM dengji"
    conditions = []
    params = []

    if query_date:
        # 处理日期格式转换 - 前端发送YYYY-MM-DD，后端存储YYYYMMDD
        try:
            # 尝试解析前端日期格式
            date_obj = datetime.datetime.strptime(query_date, '%Y-%m-%d')
            backend_date = date_obj.strftime('%Y%m%d')
        except ValueError:
            # 如果不是前端格式，可能已经是后端格式，直接使用
            backend_date = query_date
        
        conditions.append("登记日期 = ?")
        params.append(backend_date)
    
    # 处理具体的客户编号查询
    if query_customer_id:
        conditions.append("客户编号 = ?")
        params.append(query_customer_id)
    elif query_prefix:
        # Ensure prefix is a single digit '1', '2', or '3' for safety, though client sends correct one.
        if query_prefix in ['1', '2', '3', '4', '5', '6']:
            conditions.append("SUBSTR(客户编号, 1, 1) = ?") # Check only the first digit
            params.append(query_prefix)
        elif len(query_prefix) > 0: # Fallback for general prefix if needed, but less strict
             conditions.append("客户编号 LIKE ?")
             params.append(f"{query_prefix}%")

    if conditions:
        sql_query += " WHERE " + " AND ".join(conditions)
    
    sql_query += " ORDER BY 客户编号"

    cursor.execute(sql_query, tuple(params))
    dengji_entries = cursor.fetchall()
    conn.close()
    
    return jsonify([dict(row) for row in dengji_entries])

@app.route('/api/dengji', methods=['POST'])
def add_dengji():
    data = request.get_json()
    customer_id = data.get('客户编号')
    customer_name = data.get('客户名称')
    
    # 直接使用服务器当前日期，忽略前端发送的日期（避免时区问题）
    entry_date = get_current_date()

    if not customer_id or not customer_name:
        return jsonify({"error": "客户编号和客户名称不能为空"}), 400

    # Validate customer_id format (3 digits)
    if not (isinstance(customer_id, str) and customer_id.isdigit() and len(customer_id) == 3):
        return jsonify({"error": "客户编号必须是3位数字"}), 400
    
    # Validate prefix based on the first digit of customer_id
    actual_prefix = customer_id[0]
    if actual_prefix not in ['1', '2', '3', '4', '5', '6']:
        return jsonify({"error": "客户编号必须以1, 2, 3, 4, 5, 或 6 开头"}), 400

    conn = get_db_connection()
    cursor = conn.cursor()

    # Check if 客户名称 exists in danjia table
    cursor.execute("SELECT 1 FROM danjia WHERE 客户名称 = ?", (customer_name,))
    if cursor.fetchone() is None:
        conn.close()
        return jsonify({"error": "客户名称不存在于单价列表中，请先在单价管理中添加该客户。"}), 400

    try:
        cursor.execute("INSERT INTO dengji (客户编号, 客户名称, 登记日期) VALUES (?, ?, ?)",
                       (customer_id, customer_name, entry_date))
        conn.commit()
        new_id = cursor.lastrowid
    except sqlite3.IntegrityError as e:
        conn.close()
        if "UNIQUE constraint failed" in str(e):
            return jsonify({"error": f"客户编号 '{customer_id}' 在今天（{entry_date}）已存在。"}), 400
        current_app.logger.error(f"Database integrity error: {e}")
        return jsonify({"error": f"数据库错误，可能客户编号已存在或其它约束冲突。"}), 500
    except Exception as e:
        conn.close()
        current_app.logger.error(f"Error adding dengji entry: {e}")
        return jsonify({"error": f"添加登记记录时发生服务器错误。"}), 500
    finally:
        if conn: # Ensure connection is closed
            conn.close()

    return jsonify({"message": "登记成功", "id": new_id}), 201

@app.route('/api/dengji', methods=['PUT'])
def update_dengji():
    """更新登记数据"""
    try:
        data = request.json
        id = data.get('id')
        客户编号 = data.get('客户编号', '').strip()
        客户名称 = data.get('客户名称', '').strip()
        frontend_date = data.get('登记日期')
        
        # 处理登记日期格式转换
        if frontend_date:
            try:
                # 转换YYYY-MM-DD格式为YYYYMMDD格式
                date_obj = datetime.datetime.strptime(frontend_date, '%Y-%m-%d')
                登记日期 = date_obj.strftime('%Y%m%d')
            except ValueError:
                # 如果不是前端格式，可能已经是后端格式，直接使用
                登记日期 = frontend_date
        else:
            登记日期 = frontend_date
        
        if not 客户编号:
            return jsonify({
                'success': False,
                'error': '客户编号不能为空'
            }), 400
        
        # 根据客户编号查找记录并更新
        conn = get_db_connection()
        if id:
            # 通过ID更新
            conn.execute(
                'UPDATE dengji SET 客户编号 = ?, 客户名称 = ?, 登记日期 = ? WHERE id = ?',
                (客户编号, 客户名称, 登记日期, id)
            )
        else:
            # 通过客户编号更新
            conn.execute(
                'UPDATE dengji SET 客户名称 = ?, 登记日期 = ? WHERE 客户编号 = ?',
                (客户名称, 登记日期, 客户编号)
            )
        conn.commit()
        conn.close()
        
        return jsonify({
            'success': True,
            'message': '更新成功'
        })
    except sqlite3.IntegrityError:
        return jsonify({
            'success': False,
            'error': '客户编号已存在'
        }), 400
    except Exception as e:
        print(f"更新登记数据时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

@app.route('/api/dengji', methods=['DELETE'])
def delete_dengji():
    """删除登记数据"""
    try:
        data = request.json
        id = data.get('id')
        客户编号 = data.get('客户编号')
        
        if not id and not 客户编号:
            return jsonify({
                'success': False,
                'error': '缺少记录ID或客户编号'
            }), 400
        
        conn = get_db_connection()
        if id:
            conn.execute('DELETE FROM dengji WHERE id = ?', (id,))
        else:
            conn.execute('DELETE FROM dengji WHERE 客户编号 = ?', (客户编号,))
        conn.commit()
        conn.close()
        
        return jsonify({
            'success': True,
            'message': '删除成功'
        })
    except Exception as e:
        print(f"删除登记数据时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

@app.route('/api/config', methods=['GET'])
def get_config():
    """获取配置参数"""
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        
        # 获取所有配置
        cursor.execute("SELECT key, value, description FROM config ORDER BY key")
        configs = cursor.fetchall()
        conn.close()
        
        # 转换为字典格式
        config_dict = {}
        for row in configs:
            config_dict[row['key']] = {
                'value': row['value'],
                'description': row['description']
            }
        
        return jsonify({
            'success': True,
            'data': config_dict
        })
        
    except Exception as e:
        print(f"获取配置时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

@app.route('/api/today_folders', methods=['GET'])
def get_today_folders():
    """获取当天的编号_客户名称对，用于创建文件夹"""
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        
        # 获取当天的登记数据
        today = get_current_date()
        cursor.execute(
            "SELECT 客户编号, 客户名称 FROM dengji WHERE 登记日期 = ? ORDER BY 客户编号",
            (today,)
        )
        entries = cursor.fetchall()
        conn.close()
        
        # 构造编号_客户名称对
        folder_names = []
        for row in entries:
            folder_name = f"{row['客户编号']}_{row['客户名称']}"
            folder_names.append(folder_name)
        
        return jsonify({
            'success': True,
            'date': today,
            'folders': folder_names
        })
        
    except Exception as e:
        print(f"获取当天文件夹信息时出错: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

# 文件夹路径配置API
@app.route('/api/folder-path', methods=['GET'])
def get_folder_path():
    """获取保存的文件夹路径"""
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        cursor.execute("SELECT value FROM config WHERE key = 'customer_folder_path'")
        result = cursor.fetchone()
        conn.close()
        
        if result and result['value']:
            full_path = result['value']
            
            # 尝试从完整路径中分离基础路径和日期路径
            # 寻找最后一个 \ 后面是否为日期格式 (\M.D)
            import re
            # 匹配 \数字.数字 的模式（如 \6.4）
            date_pattern = r'\\(\d{1,2}\.\d{1,2})$'
            match = re.search(date_pattern, full_path)
            
            if match:
                # 如果找到日期模式，分离基础路径和日期路径
                date_folder = '\\' + match.group(1)  # \6.4
                base_path = full_path[:-len(date_folder)]  # 移除日期部分
                return jsonify({
                    "success": True, 
                    "path": base_path,
                    "fullPath": full_path,
                    "dateFolder": date_folder
                })
            else:
                # 如果没有找到日期模式，可能是旧格式的完整路径
                return jsonify({
                    "success": True, 
                    "path": full_path,
                    "fullPath": full_path,
                    "dateFolder": ""
                })
        else:
            return jsonify({
                "success": True, 
                "path": "",
                "fullPath": "",
                "dateFolder": ""
            })
    except Exception as e:
        print(f"获取文件夹路径时出错: {str(e)}")
        return jsonify({"success": False, "error": str(e)}), 500

def normalize_path(path):
    """标准化路径格式，支持多种路径格式"""
    if not path:
        return ''
    
    # 移除首尾空白字符
    path = path.strip()
    
    # 移除首尾的引号 (单引号或双引号)
    if (path.startswith('"') and path.endswith('"')) or \
       (path.startswith("'") and path.endswith("'")):
        path = path[1:-1]
    
    # 对于UNC路径，确保使用正确的反斜杠格式
    if path.startswith('\\\\'):
        # UNC路径：将所有正斜杠转换为反斜杠
        path = path.replace('/', '\\')
        # 移除多余的反斜杠，但保留开头的\\
        parts = path.split('\\')
        # 过滤空字符串，但保留前两个空字符串（对应\\）
        filtered_parts = ['', ''] + [part for part in parts[2:] if part]
        path = '\\'.join(filtered_parts)
    else:
        # 本地路径：使用系统路径分隔符
        path = os.path.normpath(path)
    
    return path

@app.route('/api/folder-path', methods=['POST'])
def save_folder_path():
    """保存文件夹路径到数据库"""
    try:
        data = request.get_json()
        base_path = data.get('basePath', '')
        date_path = data.get('datePath', '')
        
        normalized_base_path = normalize_path(base_path)
        
        if not normalized_base_path:
            return jsonify({"success": False, "error": "基础路径不能为空"}), 400
        
        if not date_path:
            return jsonify({"success": False, "error": "日期路径不能为空"}), 400
        
        # 拼接完整路径
        full_path = normalized_base_path + date_path
        full_path = normalize_path(full_path)
        
        # 对于UNC路径，跳过本地存在性检查
        is_unc_path = normalized_base_path.startswith('\\\\')
        
        if not is_unc_path:
            # 只对本地路径进行存在性检查
            if not os.path.exists(normalized_base_path):
                return jsonify({"success": False, "error": f"基础路径不存在: {normalized_base_path}"}), 400
            
            if not os.path.isdir(normalized_base_path):
                return jsonify({"success": False, "error": f"基础路径不是一个有效的文件夹: {normalized_base_path}"}), 400
        else:
            print(f"UNC基础路径已保存，跳过本地存在性检查: {normalized_base_path}")
        
        # 检查完整路径是否存在，不存在则创建
        try:
            if not os.path.exists(full_path):
                os.makedirs(full_path, exist_ok=True)
                print(f"成功创建日期文件夹: {full_path}")
            else:
                print(f"日期文件夹已存在: {full_path}")
        except Exception as e:
            # 对于UNC路径，创建失败不阻止保存
            if not is_unc_path:
                return jsonify({"success": False, "error": f"创建日期文件夹失败: {str(e)}"}), 500
            else:
                print(f"UNC路径日期文件夹创建失败，但保存继续: {str(e)}")
        
        conn = get_db_connection()
        cursor = conn.cursor()
        
        # 使用INSERT OR REPLACE来更新配置
        cursor.execute(
            "INSERT OR REPLACE INTO config (key, value, description) VALUES (?, ?, ?)",
            ('customer_folder_path', full_path, '客户文件夹存储路径')
        )
        conn.commit()
        conn.close()
        
        response_data = {
            "success": True, 
            "message": "路径保存成功",
            "basePath": normalized_base_path,
            "datePath": date_path,
            "fullPath": full_path
        }
        
        # 推送路径更新到所有客户端
        try:
            # 使用在任务统计.py文件末尾初始化的socketio实例
            socketio.emit('folder_path_updated', {
                'type': 'folder_path_updated',
                'basePath': normalized_base_path,
                'datePath': date_path,
                'fullPath': full_path,
                'message': '文件夹路径已更新'
            })
            print(f"📡 文件夹路径更新已推送到所有客户端: {full_path}")
        except Exception as push_error:
            print(f"⚠️ WebSocket推送失败，但不影响路径保存: {push_error}")
        
        # 重启文件夹监控以使用新路径
        try:
            print("🔄 路径配置已更新，重启文件夹监控...")
            xiadan.stop_folder_monitoring()
            xiadan.start_folder_monitoring()
            print("✅ 文件夹监控已重启，现在使用新的路径配置")
        except Exception as restart_error:
            print(f"⚠️ 重启文件夹监控失败: {restart_error}")
        
        return jsonify(response_data)
    except Exception as e:
        print(f"保存文件夹路径时出错: {str(e)}")
        return jsonify({"success": False, "error": str(e)}), 500



# 编号生成锁
number_locks = {
    '1': threading.Lock(),
    '2': threading.Lock(), 
    '3': threading.Lock(),
    '4': threading.Lock(),
    '5': threading.Lock(),
    '6': threading.Lock()
}

def get_next_number_safe(prefix):
    """
    获取下一个编号。假定调用此函数时，相应的 number_locks[prefix] 已被持有。
    """
    # 注意：此函数现在期望调用者已经获取了 number_locks[prefix]
    try:
        lock_file = os.path.join(base_dir, f'lock_{prefix}.txt')
        
        current_number_from_file = 1 
        if os.path.exists(lock_file):
            try:
                with open(lock_file, 'r', encoding='utf-8') as f:
                    content = f.read().strip()
                    if content:
                        current_number_from_file = int(content) + 1
            except (ValueError, IOError) as e:
                print(f"读取锁文件 {lock_file} 失败或内容无效: {e}。将从1开始。")
                current_number_from_file = 1
        
        conn_check = None
        try:
            conn_check = get_db_connection()
            cursor_check = conn_check.cursor()
            today = get_current_date()
            
            temp_number = current_number_from_file - 1 
            new_id = None

            while True:
                temp_number += 1
                new_id_candidate = f"{prefix}{temp_number:02d}"
                
                cursor_check.execute(
                    "SELECT 1 FROM dengji WHERE 客户编号 = ? AND 登记日期 = ?",
                    (new_id_candidate, today)
                )
                if cursor_check.fetchone() is None:
                    new_id = new_id_candidate
                    current_number_to_write = temp_number
                    break 

            with open(lock_file, 'w', encoding='utf-8') as f:
                f.write(str(current_number_to_write))
            
            return new_id
            
        finally:
            if conn_check:
                conn_check.close()
            
    except Exception as e:
        print(f"生成编号时出错 (prefix: {prefix}): {str(e)}")
        import traceback
        traceback.print_exc()
        return None

@app.route('/api/dengji-auto', methods=['POST'])
def add_dengji_auto():
    """自动生成编号并创建文件夹的登记接口"""
    data = request.get_json()
    customer_name = data.get('客户名称')
    prefix = data.get('prefix')
    frontend_date = data.get('登记日期')

    if not customer_name or not prefix:
        return jsonify({"success": False, "error": "参数不完整"}), 400

    if prefix not in ['1', '2', '3', '4', '5', '6']:
        return jsonify({"success": False, "error": "无效的前缀"}), 400

    entry_date_yyyymmdd = get_current_date()
    if frontend_date:
        try:
            date_obj = datetime.datetime.strptime(frontend_date, '%Y-%m-%d')
            entry_date_yyyymmdd = date_obj.strftime('%Y%m%d')
        except ValueError:
            print(f"警告: 前端提供的登记日期格式无效 '{frontend_date}'，将使用当前服务器日期。")

    conn = None
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute("SELECT 1 FROM danjia WHERE 客户名称 = ?", (customer_name,))
        if cursor.fetchone() is None:
            return jsonify({"success": False, "error": "客户名称不存在于单价列表中，请先在单价管理中添加该客户。"}), 400

        with number_locks[prefix]:
            cursor.execute(
                "SELECT 客户编号 FROM dengji WHERE 客户名称 = ? AND 登记日期 = ?",
                (customer_name, entry_date_yyyymmdd)
            )
            existing_entry = cursor.fetchone()
            if existing_entry:
                return jsonify({
                    "success": False,
                    "error": f"客户名称 '{customer_name}' 在今天已被编号 '{existing_entry['客户编号']}' 登记，请勿重复添加。"
                }), 400

            customer_id = get_next_number_safe(prefix) 

            if not customer_id:
                return jsonify({"success": False, "error": "生成客户编号失败"}), 500

            cursor.execute("SELECT value FROM config WHERE key = 'customer_folder_path'")
            path_result = cursor.fetchone()
            if not path_result or not path_result['value']:
                return jsonify({"success": False, "error": "请先设置客户文件夹存储路径"}), 400
            
            folder_path_base = normalize_path(path_result['value'])
            
            # 对于UNC路径，跳过本地存在性检查
            is_unc_path = folder_path_base.startswith('\\\\')
            if not is_unc_path and not os.path.exists(folder_path_base):
                return jsonify({"success": False, "error": "设置的文件夹路径不存在"}), 400

            folder_name_to_create = f"{customer_id}_{customer_name}"
            full_folder_path = os.path.join(folder_path_base, folder_name_to_create)
            
            # 确保UNC路径格式正确
            if is_unc_path:
                full_folder_path = full_folder_path.replace('/', '\\')
            
            try:
                os.makedirs(full_folder_path, exist_ok=True)
                print(f"成功创建文件夹: {full_folder_path}")
            except Exception as e:
                print(f"创建文件夹失败: {str(e)}")
                # 对于UNC路径，文件夹创建失败不阻止登记
                if not is_unc_path:
                    return jsonify({"success": False, "error": f"创建文件夹失败: {str(e)}"}), 500
                else:
                    print(f"UNC路径文件夹创建失败，但登记继续: {str(e)}")

            try:
                cursor.execute(
                    "INSERT INTO dengji (客户编号, 客户名称, 登记日期) VALUES (?, ?, ?)",
                    (customer_id, customer_name, entry_date_yyyymmdd)
                )
                conn.commit()
                new_db_id = cursor.lastrowid
            except sqlite3.IntegrityError as ie:
                print(f"数据库插入 IntegrityError (可能编号冲突): {ie} for {customer_id}, {customer_name}, {entry_date_yyyymmdd}")
                try:
                    if os.path.exists(full_folder_path) and os.path.isdir(full_folder_path) and not os.listdir(full_folder_path):
                        os.rmdir(full_folder_path)
                except Exception as e_rmdir:
                    print(f"清理文件夹失败: {e_rmdir}")
                return jsonify({"success": False, "error": f"数据库错误：客户编号 '{customer_id}' 可能已在当天登记。"}), 409

        return jsonify({
            'success': True,
            'id': new_db_id,
            'customer_id': customer_id,
            'folder_path': full_folder_path,
            'message': f'成功添加客户 {customer_name}，编号：{customer_id}，文件夹已创建'
        })

    except Exception as e:
        print(f"自动添加登记数据时出错: {str(e)}")
        import traceback
        traceback.print_exc()
        return jsonify({"success": False, "error": f"服务器内部错误: {str(e)}"}), 500
    finally:
        if conn:
            conn.close()

# 获取当前日期文件夹名称
@app.route('/api/current_date_folder', methods=['GET'])
def get_current_date_folder():
    """获取当前日期的文件夹名称，格式为 \\M.D"""
    try:
        now = datetime.datetime.now()
        month = now.month
        day = now.day
        folder_name = f"\\{month}.{day}"
        return jsonify({
            'success': True,
            'date_folder': folder_name
        })
    except Exception as e:
        print(f"获取当前日期文件夹失败: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

# 注入依赖函数到xiadan模块
xiadan.get_db_connection = get_db_connection
xiadan.normalize_path = normalize_path
xiadan.get_next_number_safe = get_next_number_safe
xiadan.get_current_date = get_current_date
xiadan.get_local_ip = get_local_ip

# 初始化订单数据库
try:
    conn = get_db_connection()
    xiadan.init_orders_database(conn)
    conn.close()
    print("订单数据库初始化完成")
except Exception as e:
    print(f"订单数据库初始化失败: {str(e)}")

# 初始化SocketIO
# 明确指定async_mode以解决PyInstaller打包问题
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')

# 设置SocketIO实例到xiadan模块
xiadan.set_socketio_instance(socketio)

# 注册xiadan路由
xiadan.register_xiadan_routes(app)

# 初始化扫描
scan_directory()

# 启动后台数据更新线程
update_thread = threading.Thread(target=update_data_periodically, daemon=True)
update_thread.start()

# 异步启动文件夹监控，不阻塞Web服务器启动
def start_folder_monitoring_async():
    """异步启动文件夹监控"""
    print("正在后台启动文件夹监控...")
    try:
        xiadan.start_folder_monitoring()
    except Exception as e:
        print(f"文件夹监控启动失败: {str(e)}")

folder_monitoring_thread = threading.Thread(target=start_folder_monitoring_async, daemon=True)
folder_monitoring_thread.start()

# 主函数入口
if __name__ == "__main__":
    # 获取本机局域网IP地址
    local_ip = get_local_ip()
    
    # 检查www文件夹是否存在
    www_folder = os.path.join(base_dir, 'www')
    ensure_log_file_exists() # 确保日志文件在应用启动时被检查或创建

    if not os.path.exists(www_folder):
        os.makedirs(www_folder, exist_ok=True)
        print(f"创建www文件夹: {www_folder}")
    
    # 检查需要的HTML文件是否存在
    dashboard_file = os.path.join(www_folder, 'dashboard.html')
    baogong_file = os.path.join(www_folder, 'baogong.html')
    upload_file = os.path.join(www_folder, 'upload.html')
    
    if not os.path.exists(dashboard_file):
        print(f"警告: dashboard.html文件不存在: {dashboard_file}")
        print("请确保在www文件夹中放置dashboard.html文件")
    
    if not os.path.exists(baogong_file):
        print(f"警告: baogong.html文件不存在: {baogong_file}")
        print("请确保在www文件夹中放置baogong.html文件")
    
    if not os.path.exists(upload_file):
        print(f"警告: upload.html文件不存在: {upload_file}")
        print("请确保在www文件夹中放置upload.html文件")
    
    print(f"创建数据大屏系统，正在启动服务器...")
    print(f"本机IP地址: {local_ip}")
    print(f"✅ Web服务器启动中，即将可以访问...")
    print(f"📊 访问地址: http://{local_ip}:{端口号}")
    print(f"📋 下单管理: http://{local_ip}:{端口号}/xiadan")
    print(f"💰 单价管理: http://{local_ip}:{端口号}/danjia")
    print(f"📝 编号登记: http://{local_ip}:{端口号}/dengji")
    print(f"🔧 图片压缩功能已启用，使用 /compress_images 端点可压缩所有图片到约5KB大小")
    print(f"📁 PDF上传功能已启用，访问 http://{local_ip}:{端口号}/upload 可上传PDF文件")
    print(f"🔄 文件夹监控正在后台启动，实时推送功能稍后可用")
    
    # 自动打开浏览器页面
    open_browser_pages(local_ip, 端口号)

    # 启动Flask应用 (使用SocketIO)
    print(f"🚀 启动Web服务器...")
    socketio.run(app, host='0.0.0.0', port=端口号, debug=False, allow_unsafe_werkzeug=True)